package com.mojavelinux.open18;

import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.RandomAccess;
import java.util.Set;

import org.jboss.seam.annotations.Name;

/**
 * <p>Transforms a {@link Collection} to a {@link List} interface
 * so that it can be used within a JSF iteration context.</p>
 *
 * <p>JSF only supports iteration over a List, so it is necessary to
 * view the collection through this interface.</p>
 *
 * <p>Register this class as a managed bean in faces-config.xml, or expose
 * as a Seam component.</p>
 *
 * <pre>
 * &lt;managed-bean&gt;
 *  &lt;managed-bean-name&gt;transformCollection&lt;/managed-bean-name&gt;
 *  &lt;managed-bean-class&gt;com.coderyte.bassline.jsf.convert.CollectionTransformer&lt;/managed-bean-class&gt;
 *  &lt;managed-bean-scope&gt;request&lt;/managed-bean-scope&gt;
 * &lt;/managed-bean&gt;
 * </pre>
 *
 * <p>It can then be used on an arbitrary collection, such as a {@link Set},
 * using the following snippet.</p>
 *
 * <pre>
 *  &lt;h:dataTable value="#{transformCollection.toList[mySet]}"&gt;
 * </pre>
 *
 * @author Dan Allen <dan.allen@mojavelinux.com>
 */
@Name("transformCollection")
public class CollectionTransformer {

	private Map<Collection<Object>, List> map;

	private int size;

	private static final int INITIAL_CAPACITY = 10;
	
	public CollectionTransformer() {
		map = new ListFacade();
		size = INITIAL_CAPACITY;
	}

	public Map<Collection<Object>, List> getToList() {
		return map;
	}

	public int getSize() {
		return size;
	}

	public void setSize( int size ) {
		this.size = size;
	}

	private class ListFacade extends AbstractMap<Collection<Object>, List> {
		public List get( Object o ) {
			if ( !( o instanceof Collection ) ) {
				return null;
			}

			// Just send RandomAccess lists out; wrap any other Collection
			// into a List
			if ( ( o instanceof List ) && ( o instanceof RandomAccess ) ) {
				return (List) o;
			}

			@SuppressWarnings("unchecked")
			Collection<Object> c = (Collection<Object>) o;
			if ( c.isEmpty() ) {
				return Collections.EMPTY_LIST;
			}

			return new ListImpl<Object>( c, getSize() );
		}

		public Set<Map.Entry<Collection<Object>, List>> entrySet() {
			throw new NoSuchMethodError( "This method is not relevant for this implementation." );
		}
	}

	static private class ListImpl<E> extends AbstractList<E> {

		private final Collection<E> c;

		private final int bufferSize;

		private final int cSize;

		private int cOffset;

		private ArrayList<E> buffer;
		
		public ListImpl( Collection<E> c, int size ) {
			this.c = c;
			this.cSize = c.size();
			if ( size == 0 ) {
				bufferSize = cSize;
			}
			else {
				bufferSize = Math.min( size, cSize );
			}
				
			buffer = new ArrayList<E>( bufferSize );
			cOffset = -1;
		}

		public int size() {
			return cSize;
		}

		public E get( int index ) {
			if ( ( index < 0 ) || ( index >= cSize ) ) {
				throw new IndexOutOfBoundsException();
			}

			int offset = ( index / bufferSize ) * bufferSize;
			if ( offset != cOffset ) {
				loadBuffer( offset );
				cOffset = offset;
			}

			return buffer.get( index - cOffset );
		}

		private void loadBuffer( int offset ) {
			Iterator<E> iter = c.iterator();
			int i = 0;

			while ( i < offset ) {
				assert iter.hasNext();
				iter.next();
				i++;
			}

			buffer.clear();

			int count = 0;
			while ( ( count < bufferSize ) && ( i < cSize ) ) {
				assert iter.hasNext();
				buffer.add( iter.next() );
				i++;
				count++;
			}
		}

	}

}
